package net.ossrs.yasea;

import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.zaker.rtmp.RtmpPublisher;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Created by Leo Ma on 2016/7/25.
 */
public class SrsPublisher implements SurfaceHolder.Callback, Camera.PreviewCallback {
  private static final String TAG = "SrsPublisher";

  private AudioRecord mic;
  private boolean aloop = false;
  private Thread aworker;

  private SurfaceView mCameraView;
  private Camera mCamera;

  private boolean sendAudioOnly = false;
  private int videoFrameCount;
  private long lastTimeMillis;
  private int mPreviewRotation = 90;
  private int mCamId = -1;//Camera.getNumberOfCameras() - 1;
  private int mCamPosition = Camera.CameraInfo.CAMERA_FACING_BACK;
  private double mSamplingFps;
  private byte[] mYuvPreviewFrame;

  private SrsFlvMuxer mFlvMuxer;
  private SrsMp4Muxer mMp4Muxer;
  private SrsEncoder mEncoder = new SrsEncoder();

  public SrsPublisher() {
  }

  public void startEncode() {
    if (!mEncoder.start()) {
      return;
    }

    startCamera();

    aworker = new Thread(new Runnable() {
      @Override
      public void run() {
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
        startAudio();
      }
    });
    aloop = true;
    aworker.start();
  }

  public void stopEncode() {
    stopAudio();
    stopCamera();
    mEncoder.stop();
  }

  public void startPublish(String rtmpUrl) {
    if (mFlvMuxer != null) {
      try {
        mFlvMuxer.start(rtmpUrl);
      } catch (IOException e) {
        e.printStackTrace();
        return;
      }
      mFlvMuxer.setVideoResolution(mEncoder.getOutputWidth(), mEncoder.getOutputHeight());

      startEncode();
    }
  }

  public void stopPublish() {
    if (mFlvMuxer != null) {
      stopEncode();
      mFlvMuxer.stop();
    }
  }

  public void startRecord(String recPath) {
    if (mMp4Muxer != null) {
      try {
        mMp4Muxer.record(new File(recPath));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  public void stopRecord() {
    if (mMp4Muxer != null) {
      mMp4Muxer.stop();
    }
  }

  public void pauseRecord() {
    if (mMp4Muxer != null) {
      mMp4Muxer.pause();
    }
  }

  public void resumeRecord() {
    if (mMp4Muxer != null) {
      mMp4Muxer.resume();
    }
  }

  public void swithToSoftEncoder() {
    mEncoder.swithToSoftEncoder();
  }

  public void swithToHardEncoder() {
    mEncoder.swithToHardEncoder();
  }

  public boolean isSoftEncoder() {
    return mEncoder.isSoftEncoder();
  }

  public double getmSamplingFps() {
    return mSamplingFps;
  }

  public int getCamraId() {
    return mCamId;
  }

  public int getNumberOfCameras() {
    return mCamera != null ? mCamera.getNumberOfCameras() : -1;
  }

  public void setPreviewResolution(int width, int height) {
    mEncoder.setPreviewResolution(width, height);
  }

  public void setOutputResolution(int width, int height) {
    mEncoder.setPortraitResolution(width, height);
  }

  public void setScreenOrientation(int orientation) {
    mEncoder.setScreenOrientation(orientation);
  }

  public void setPreviewRotation(int rotation) {
    mPreviewRotation = rotation;
  }

  public void setVideoHDMode() {
    mEncoder.setVideoHDMode();
  }

  public void setVideoSmoothMode() {
    mEncoder.setVideoSmoothMode();
  }

  public void setSendAudioOnly(boolean flag) {
    sendAudioOnly = flag;
  }

  public void switchCameraFace() {

    if(mCamPosition == Camera.CameraInfo.CAMERA_FACING_BACK){
      mCamPosition = Camera.CameraInfo.CAMERA_FACING_FRONT;
    }else {
      mCamPosition = Camera.CameraInfo.CAMERA_FACING_BACK;
    }
    mCamId = findPositionCamera(mCamPosition);
    stopCamera();
    mEncoder.swithCameraFace();
    startCamera();
  }

  private int findPositionCamera(int position) {
    int cameraCount = 0;
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    cameraCount = Camera.getNumberOfCameras(); // get cameras number

    for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
      Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo
      if (cameraInfo.facing == position) {
        // 代表摄像头的方位，目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置
        return camIdx;
      }
    }
    return -1;
  }

  private void startCamera() {
    if (mCamera != null) {
      return;
    }

    if(mCamId == -1){
      mCamId = findPositionCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
    }

    Log.i(TAG, " startCamera mCamId: " + mCamId);

    if (mCamId > (Camera.getNumberOfCameras() - 1) || mCamId < 0) {
      return;
    }

    mCamera = Camera.open(mCamId);

    Camera.Parameters params = mCamera.getParameters();
    Camera.Size size = mCamera.new Size(mEncoder.getPreviewWidth(), mEncoder.getPreviewHeight());

    List<Camera.Size> supportedPreviewSizes = params.getSupportedPreviewSizes();
    for (Camera.Size preview : supportedPreviewSizes) {
      Log.i("TAG", "supportedPreviewSizes w: " + preview.width + " - h: " + preview.height);
    }
    List<Camera.Size> supportedPictureSizes = params.getSupportedPictureSizes();
    for (Camera.Size picture : supportedPictureSizes) {
      Log.i("TAG", "supportedPictureSizes w: " + picture.width + " - h: " + picture.height);
    }

//    if (!params.getSupportedPreviewSizes().contains(size) || !params.getSupportedPictureSizes().contains(size)) {
//      Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
//          new IllegalArgumentException(String.format("Unsupported resolution %dx%d", size.width, size.height)));
//    }

    final Camera.Size bestSize = getBestPreviewSize(mEncoder.getPreviewWidth(),
        mEncoder.getPreviewHeight(), params);

    mYuvPreviewFrame = new byte[mEncoder.getPreviewWidth() * mEncoder.getPreviewHeight() * 3 / 2];

    /***** set parameters *****/
    //params.set("orientation", "portrait");
    //params.set("orientation", "landscape");
    //params.setRotation(180);

//    params.setPictureSize(mEncoder.getPreviewWidth(), mEncoder.getPreviewHeight());
//    params.setPreviewSize(mEncoder.getPreviewWidth(), mEncoder.getPreviewHeight());

    params.setPictureSize(bestSize.width, bestSize.height);
    params.setPreviewSize(bestSize.width, bestSize.height);

    int[] range = findClosestFpsRange(SrsEncoder.VFPS, params.getSupportedPreviewFpsRange());
    params.setPreviewFpsRange(range[0], range[1]);
    params.setPreviewFormat(ImageFormat.NV21);
    params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
    params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
    if (!params.getSupportedFocusModes().isEmpty()) {
      params.setFocusMode(params.getSupportedFocusModes().get(0));
    }
    mCamera.setParameters(params);

    mCamera.setDisplayOrientation(mPreviewRotation);

    mCamera.addCallbackBuffer(mYuvPreviewFrame);
    mCamera.setPreviewCallbackWithBuffer(this);
    try {
      if (mCameraView != null) {
        mCamera.setPreviewDisplay(mCameraView.getHolder());
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    mCamera.startPreview();
  }

  private Camera.Size getBestPreviewSize(int width, int height, Camera.Parameters parameters) {
    Camera.Size result = null;
    for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
      if (size.width <= width && size.height <= height) {
        if (result == null) {
          result = size;
        } else {
          int resultArea = result.width * result.height;
          int newArea = size.width * size.height;
          if (newArea > resultArea) {
            result = size;
          }
        }
      }
    }

    Log.i("TouchTv", " getBestPreviewSize targetWidth: " + width
        + " targetHeight: " + height + " resultWidth: "
        + result.width + " resultHeight: " + result.height);

    return (result);
  }

  private static int[] findClosestFpsRange(int expectedFps, List<int[]> fpsRanges) {
    expectedFps *= 1000;
    int[] closestRange = fpsRanges.get(0);
    int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
    for (int[] range : fpsRanges) {
      if (range[0] <= expectedFps && range[1] >= expectedFps) {
        int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
        if (curMeasure < measure) {
          closestRange = range;
          measure = curMeasure;
        }
      }
    }
    return closestRange;
  }

  private void stopCamera() {
    if (mCamera != null) {
      // need to SET NULL CB before stop preview!!!
      mCamera.setPreviewCallback(null);
      mCamera.stopPreview();
      mCamera.release();
      mCamera = null;
    }
  }

  private void onGetYuvFrame(byte[] data) {
    // Calculate YUV sampling FPS
    if (videoFrameCount == 0) {
      lastTimeMillis = System.nanoTime() / 1000000;
      videoFrameCount++;
    } else {
      if (++videoFrameCount >= 48) {
        long diffTimeMillis = System.nanoTime() / 1000000 - lastTimeMillis;
        mSamplingFps = (double) videoFrameCount * 1000 / diffTimeMillis;
        videoFrameCount = 0;
      }
    }

    if (!sendAudioOnly) {
      mEncoder.onGetYuvFrame(data);
    }
  }

  private void startAudio() {
    if (mic != null) {
      return;
    }

    int bufferSize = 2 * AudioRecord.getMinBufferSize(SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, AudioFormat.ENCODING_PCM_16BIT);
    mic = new AudioRecord(MediaRecorder.AudioSource.MIC, SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
    mic.startRecording();

    byte pcmBuffer[] = new byte[4096];
    while (aloop && !Thread.interrupted()) {
      int size = mic.read(pcmBuffer, 0, pcmBuffer.length);
      if (size <= 0) {
        break;
      }
      mEncoder.onGetPcmFrame(pcmBuffer, size);
    }
  }

  private void stopAudio() {
    aloop = false;
    if (aworker != null) {
      aworker.interrupt();
      try {
        aworker.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
        aworker.interrupt();
      }
      aworker = null;
    }

    if (mic != null) {
      mic.setRecordPositionUpdateListener(null);
      mic.stop();
      mic.release();
      mic = null;
    }
  }

  public void setPublishEventHandler(RtmpPublisher.EventHandler handler) {
    mFlvMuxer = new SrsFlvMuxer(handler);
    mEncoder.setFlvMuxer(mFlvMuxer);
  }

  public void setRecordEventHandler(SrsMp4Muxer.EventHandler handler) {
    mMp4Muxer = new SrsMp4Muxer(handler);
    mEncoder.setMp4Muxer(mMp4Muxer);
  }

  public void setSurfaceView(SurfaceView surfaceView) {
    mCameraView = surfaceView;
    mCameraView.getHolder().addCallback(this);
  }

  @Override
  public void onPreviewFrame(byte[] data, Camera camera) {
    onGetYuvFrame(data);

    Log.i("TouchTv", " onPreviewFrame is run ~ ");

    camera.addCallbackBuffer(mYuvPreviewFrame);
  }

  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  }

  @Override
  public void surfaceCreated(SurfaceHolder arg0) {
    if (mCamera != null) {
      try {
        mCamera.setPreviewDisplay(mCameraView.getHolder());
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public void surfaceDestroyed(SurfaceHolder arg0) {
  }
}
